﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DynamicProperties.Tests
{
	[TestClass]
	public class ChangeEventTests
	{
		[TestMethod]
		public void ChangeEventGlobalBasicTest()
		{
			var th = new ChangeTestsHelper<string, int>();

			// define property
			var testProperty1 = new DynamicProperty<string, int>("Test property 1", th.Delegate /*link global "OnChange" to helper*/);

			string obj1 = "A";
			string obj2 = "B";

			// call 3x change and check if "OnChange" called correctly with correct parameters

			testProperty1.Set(obj1, 10);
			Assert.AreEqual(1, th.CallCount); // how many changes
			Assert.AreSame(obj1, th.LastTarget); // target object
			Assert.AreEqual(0, th.LastArgs.OldValue); // old value
			Assert.AreEqual(10, th.LastArgs.NewValue); // new value
			Assert.AreSame(testProperty1, th.LastArgs.Property); // correct property

			testProperty1.Set(obj2, 20);
			Assert.AreEqual(2, th.CallCount); // how many changes
			Assert.AreSame(obj2, th.LastTarget); // target object
			Assert.AreEqual(0, th.LastArgs.OldValue); // old value
			Assert.AreEqual(20, th.LastArgs.NewValue); // new value
			Assert.AreSame(testProperty1, th.LastArgs.Property); // correct property

			testProperty1.Set(obj1, 30);
			Assert.AreEqual(3, th.CallCount); // how many changes
			Assert.AreSame(obj1, th.LastTarget); // target object
			Assert.AreEqual(10, th.LastArgs.OldValue); // old value
			Assert.AreEqual(30, th.LastArgs.NewValue); // new value
			Assert.AreSame(testProperty1, th.LastArgs.Property); // correct property
		}

		[TestMethod]
		public void ChangeEventBasicTest()
		{
			var th1 = new ChangeTestsHelper<string, int>();
			var th2 = new ChangeTestsHelper<string, int>();

			// define property
			var testProperty1 = new DynamicProperty<string, int>("Test property 1");

			string obj1 = "A";
			string obj2 = "B";

			// link "OnChanged" to specific instances
			Extender.Global.RegisterOnChanged(obj1, testProperty1, th1.Delegate /*link "OnChange" to helper*/);
			Extender.Global.RegisterOnChanged(obj2, testProperty1, th2.Delegate /*link "OnChange" to helper*/);

			// call 3x change and check if "OnChange" called correctly with correct parameters

			// call 1 - change "obj1"
			testProperty1.Set(obj1, 10);
			
			Assert.AreEqual(1, th1.CallCount); // how many changes on "obj1"
			Assert.AreSame(obj1, th1.LastTarget); // target object
			Assert.AreEqual(0, th1.LastArgs.OldValue); // old value
			Assert.AreEqual(10, th1.LastArgs.NewValue); // new value
			Assert.AreSame(testProperty1, th1.LastArgs.Property); // correct property

			Assert.AreEqual(0, th2.CallCount); // how many changes on "obj2"
			Assert.IsNull(null, th2.LastTarget); // target object
			Assert.IsNull(th2.LastArgs);

			// call 2 - change "obj2"
			testProperty1.Set(obj2, 20);

			Assert.AreEqual(1, th1.CallCount); // how many changes on "obj1"
			Assert.AreSame(obj1, th1.LastTarget); // target object
			Assert.AreEqual(0, th1.LastArgs.OldValue); // old value
			Assert.AreEqual(10, th1.LastArgs.NewValue); // new value
			Assert.AreSame(testProperty1, th1.LastArgs.Property); // correct property
			
			Assert.AreEqual(1, th2.CallCount); // how many changes on "obj2"
			Assert.AreSame(obj2, th2.LastTarget); // target object
			Assert.AreEqual(0, th2.LastArgs.OldValue); // old value
			Assert.AreEqual(20, th2.LastArgs.NewValue); // new value
			Assert.AreSame(testProperty1, th2.LastArgs.Property); // correct property

			// call 3 - change "obj1"
			testProperty1.Set(obj1, 30);

			Assert.AreEqual(2, th1.CallCount); // how many changes on "obj1"
			Assert.AreSame(obj1, th1.LastTarget); // target object
			Assert.AreEqual(10, th1.LastArgs.OldValue); // old value
			Assert.AreEqual(30, th1.LastArgs.NewValue); // new value
			Assert.AreSame(testProperty1, th1.LastArgs.Property); // correct property

			Assert.AreEqual(1, th2.CallCount); // how many changes on "obj2"
			Assert.AreSame(obj2, th2.LastTarget); // target object
			Assert.AreEqual(0, th2.LastArgs.OldValue); // old value
			Assert.AreEqual(20, th2.LastArgs.NewValue); // new value
			Assert.AreSame(testProperty1, th2.LastArgs.Property); // correct property
		}

		[TestMethod]
		public void ChangeEventMultipleSubscribersTest()
		{
			var th1 = new ChangeTestsHelper<string, int>();
			var th2 = new ChangeTestsHelper<string, int>();

			// define property
			var testProperty1 = new DynamicProperty<string, int>("Test property 1");

			string obj1 = "A";

			// link "OnChanged" to specific instance
			Extender.Global.RegisterOnChanged(obj1, testProperty1, th1.Delegate /*link "OnChange" to helper*/);
			Extender.Global.RegisterOnChanged(obj1, testProperty1, th2.Delegate /*link "OnChange" to helper*/);

			// call 1 - change "obj1"
			testProperty1.Set(obj1, 10);

			// check correct values for both events
			foreach (var th in new[] { th1, th2 })
			{
				Assert.AreEqual(1, th.CallCount); // how many changes on "obj1"
				Assert.AreSame(obj1, th.LastTarget); // target object
				Assert.AreEqual(0, th.LastArgs.OldValue); // old value
				Assert.AreEqual(10, th.LastArgs.NewValue); // new value
				Assert.AreSame(testProperty1, th.LastArgs.Property); // correct property
			}
		}

		enum TestEnum { X1 = 0, X2, X3 }

		[TestMethod]
		public void ChangeEventMultipleTest()
		{
			var th1 = new ChangeTestsHelper<string, int>();
			var th2 = new ChangeTestsHelper<string, TestEnum>();

			// define property
			var testProperty1 = new DynamicProperty<string, int>("Test property 1");
			var testProperty2 = new DynamicProperty<string, TestEnum>("Test property 2");
			
			string obj1 = "A";

			// link "OnChanged" to properties
			Extender.Global.RegisterOnChanged(obj1, testProperty1, th1.Delegate /*link "OnChange" to helper*/);
			Extender.Global.RegisterOnChanged(obj1, testProperty2, th2.Delegate /*link "OnChange" to helper*/);

			// call 2x change and check if "OnChange" called correctly with correct parameters for correct properties

			// call 1 - change "testProperty1"
			testProperty1.Set(obj1, 10);

			Assert.AreEqual(1, th1.CallCount); // how many changes on "testProperty1"
			Assert.AreSame(obj1, th1.LastTarget); // target object
			Assert.AreEqual(0, th1.LastArgs.OldValue); // old value
			Assert.AreEqual(10, th1.LastArgs.NewValue); // new value
			Assert.AreSame(testProperty1, th1.LastArgs.Property); // correct property

			Assert.AreEqual(0, th2.CallCount); // how many changes on "testProperty2"
			Assert.IsNull(null, th2.LastTarget); // target object
			Assert.IsNull(th2.LastArgs);

			// call 2 - change "testProperty2"
			testProperty2.Set(obj1, TestEnum.X2);

			Assert.AreEqual(1, th1.CallCount); // how many changes on "testProperty1"
			Assert.AreSame(obj1, th1.LastTarget); // target object
			Assert.AreEqual(0, th1.LastArgs.OldValue); // old value
			Assert.AreEqual(10, th1.LastArgs.NewValue); // new value
			Assert.AreSame(testProperty1, th1.LastArgs.Property); // correct property

			Assert.AreEqual(1, th2.CallCount); // how many changes on "testProperty2"
			Assert.AreSame(obj1, th2.LastTarget); // target object
			Assert.AreEqual(TestEnum.X1, th2.LastArgs.OldValue); // old value
			Assert.AreEqual(TestEnum.X2, th2.LastArgs.NewValue); // new value
			Assert.AreSame(testProperty2, th2.LastArgs.Property); // correct property
		}

		[TestMethod]
		public void ChangeEventMoreSusbcribersOnMorePropertiesUnregisterTest()
		{
			var th1 = new ChangeTestsHelper<string, int>();
			var th2 = new ChangeTestsHelper<string, TestEnum>();

			// define property
			var testProperty1 = new DynamicProperty<string, int>("Test property 1");
			var testProperty2 = new DynamicProperty<string, TestEnum>("Test property 2");

			string obj1 = "A";

			// link "OnChanged" to properties
			Extender.Global.RegisterOnChanged(obj1, testProperty1, th1.Delegate /*link "OnChange" to helper*/);
			Extender.Global.RegisterOnChanged(obj1, testProperty2, th2.Delegate /*link "OnChange" to helper*/);

			// call 1 - change both properties
			testProperty1.Set(obj1, 10);
			testProperty2.Set(obj1, TestEnum.X2);

			// both "OnChange" should increase "CallCount" from 0 to 1
			Assert.AreEqual(1, th1.CallCount); // how many changes on "testProperty1"
			Assert.AreEqual(1, th2.CallCount); // how many changes on "testProperty2"

			// unregister "th1.Delegate" subscriber
			Extender.Global.UnregisterOnChanged(obj1, testProperty1, th1.Delegate);

			// call 2 - change both propreties
			testProperty1.Set(obj1, 20);
			testProperty2.Set(obj1, TestEnum.X3);

			// "CallCount" should increase from 1 to 2 only for "testProperty2"
			Assert.AreEqual(1, th1.CallCount); // how many changes on "th1.Delegate"
			Assert.AreEqual(2, th2.CallCount); // how many changes on "th2.Delegate"
		}

		[TestMethod]
		public void ChangeEventSubscriberOnSinglePropertyUnregisterTest()
		{
			var th1 = new ChangeTestsHelper<string, int>();
			
			// define property
			var testProperty1 = new DynamicProperty<string, int>("Test property 1");

			string obj1 = "A";

			// link "OnChanged" on property
			Extender.Global.RegisterOnChanged(obj1, testProperty1, th1.Delegate /*link "OnChange" to helper*/);

			// call 1
			testProperty1.Set(obj1, 10);

			// both "OnChange" should increase "CallCount" from 0 to 1
			Assert.AreEqual(1, th1.CallCount); // how many changes on "testProperty1"

			// unregister "th1"
			Extender.Global.UnregisterOnChanged(obj1, testProperty1, th1.Delegate);

			// call 2
			testProperty1.Set(obj1, 20);
			
			// "CallCount" should remain on same value
			Assert.AreEqual(1, th1.CallCount); // unregistered delegate
		}

		[TestMethod]
		public void ChangeEventMoreSubscribersOnSinglePropertyUnregisterTest()
		{
			var th1 = new ChangeTestsHelper<string, int>();
			var th2 = new ChangeTestsHelper<string, int>();

			// define property
			var testProperty1 = new DynamicProperty<string, int>("Test property 1");

			string obj1 = "A";

			// link "OnChanged" on property
			Extender.Global.RegisterOnChanged(obj1, testProperty1, th1.Delegate /*link "OnChange" to helper*/);
			Extender.Global.RegisterOnChanged(obj1, testProperty1, th2.Delegate /*link "OnChange" to helper*/);

			// call 1
			testProperty1.Set(obj1, 10);

			// both "OnChange" should increase "CallCount" from 0 to 1
			Assert.AreEqual(1, th1.CallCount); // how many changes on "testProperty1"
			Assert.AreEqual(1, th2.CallCount); // how many changes on "testProperty1"

			// unregister "th1"
			Extender.Global.UnregisterOnChanged(obj1, testProperty1, th1.Delegate/*unregister "OnChange" for 1/2 events */);

			// call 2
			testProperty1.Set(obj1, 20);

			// "CallCount" should increase from 1 to 2 only for "testProperty2"
			Assert.AreEqual(1, th1.CallCount); // unregistered delegate
			Assert.AreEqual(2, th2.CallCount); // still registered delegate
		}

		[TestMethod]
		public void ChangeToSameValueShouldInvokeEventTest()
		{
			var th1 = new ChangeTestsHelper<string, int>();
			var th2 = new ChangeTestsHelper<string, int>();

			// define property
			var testProperty1 = new DynamicProperty<string, int>("Test property 1", th1.Delegate/*link global "OnChange" to helper*/);

			string obj1 = "A";

			// link "OnChanged" to properties
			Extender.Global.RegisterOnChanged(obj1, testProperty1, th2.Delegate /*link local "OnChange" to helper*/);

			// change property 3x
			testProperty1.Set(obj1, 10);
			testProperty1.Set(obj1, 10);
			testProperty1.Set(obj1, 10);
			
			// value was change 3x
			Assert.AreEqual(3, th1.CallCount);
			Assert.AreEqual(3, th2.CallCount);
		}

		[TestMethod]
		public void ObjectResetWillBreakEventsTest()
		{
			var th = new ChangeTestsHelper<string, int>();

			// define property
			var testProperty1 = new DynamicProperty<string, int>("Test property 1");

			string obj1 = "A";

			// link "obj1" OnChanged property to test helper
			Extender.Global.RegisterOnChanged(obj1, testProperty1, th.Delegate /*link "OnChange" to helper*/ );

			// set value
			testProperty1.Set(obj1, 10); // first change
			Assert.AreEqual(1, th.CallCount); // "OnChanged" should be called once

			// reset object
			Extender.Global.ResetObject(obj1);

			// set value again
			testProperty1.Set(obj1, 20); // second change

			// event "OnChange" should not be invoked - it is because by calling "ResetObject" all dynamic properties and registered events should be removed
			Assert.AreEqual(1, th.CallCount);
		}
	}
}
